package nth.bananas.solving;

import java.util.Collection;
import java.util.Set;

/**
 * Maintains state of a puzzle.
 * 
 * The puzzle consists of a set of Letters, and their position on the board.
 * A board for <code>n</code> tiles is a n x n grid. The lower right position on
 * this grid is (0, 0). Negative coordinates are not allowed.
 * 
 * This API has much redundancy.
 * 
 * @author Nick Heiner
 * @see Letter
 *
 */
public interface Puzzle {
	
	/**
	 * Adds a letter to the free pieces.
	 * @param t	the letter to add
	 */
	public void addFreeLetter(Letter t);
	
	/**
	 * Removes a letter that is not on the board.
	 * This may throw a null pointer exception if <code>r</code> is null.
	 * 
	 * @param r	the letter to remove
	 * @return	true if a letter was removed, false otherwise.
	 */
	public boolean removeFreeLetter(Letter r);
	
	/**
	 * Makes sure that there are empty spots on the board for every piece.
	 * If the board currently has n tiles, this will make sure that the 
	 * board is at least n x n. 
	 */
	public void fillOutEmptyBoard();
	
	/**
	 * Gets an immutable view of the GridSpots of this puzzle.
	 * @return	the spots of this puzzle
	 */
	public Set<GridSpot<Letter>> getSpots();

	/**
	 * Returns an immutable collection of spots adjacent to <code>spot</code>.
	 * For the spot (1, 1), adjacent spots would be: <pre>
	 * 		(0, 1)
	 * 		(2, 1)
	 * 		(1, 0)
	 * 		(1, 2)
	 * </pre>
	 * If these spots do not all exist in the puzzle, they will not be returned.
	 * 
	 * @param spot
	 * @return
	 */
	public Collection<? extends GridSpot<Letter>> adjacentTo(GridSpot<Letter> spot);

	/**
	 * Creates a new Puzzle that is identical to the caller with <code>spot</code> removed.
	 * @param spot	the spot that will be excluded
	 * @return		a new Puzzle object without spot
	 */
	public Puzzle copyWithoutSpot(GridSpot<Letter> spot);

	/**
	 * Creates a new Puzzle that is identical to the caller with <code>spot</code> added.
	 * @param ps	the spot to add
	 * @return		a new Puzzle object with spot
	 */
	public Puzzle withFilled(GridSpot<Letter> ps);

	/**
	 * Gets the number of pieces that the puzzle has, but are not in some spot.
	 * @return	the number of unplaced pieces
	 */
	public int totalFreePieces();
	
	/**
	 * Gets the total number of pieces in the puzzle
	 * @return	the total number of pieces
	 */
	public int totalLetters();
	
	/**
	 * Gets a random tile from the puzzle.
	 * Exists only for testing. cocks.
	 * @return
	 */
	public Letter getRandomTile();

	/**
	 * Adds a spot to the puzzle. Negative spot coordinates will 
	 * cause an exception to be thrown, as will coordinates that are 
	 * outside the bounds of the puzzle. (For a discussion of puzzle bounds,
	 * see the interface description.)
	 * 
	 * @param ps						the spot to add
	 * @return							true if the Puzzle changed as a result of this operation
	 * @throws IllegalArgumentException	if one of the coordinates is illegal, or a tile already exists here
	 */
	public boolean addSpot(GridSpot<Letter> ps) throws IllegalArgumentException;
	
	/**
	 * Checks if a spot exists at coordinate <code>(x, y)</code>
	 * @param x	the x-coord
	 * @param y	the y-coord
	 * @return	true if a spot exists at the given coordinates
	 */
	public boolean spotExists(int x, int y);
	
	/**
	 * Returns the spot that exists at coordinates <code>(x, y)</code>. If no such spot exists, returns null.
	 * @param x	the x-coord
	 * @param y	the y-coord
	 * @return	the spot that exists, or null if nothing is there
	 */
	public GridSpot<Letter> getSpot(int x, int y);
	
	/**
	 * Returns a new puzzle whose grid is the union of the caller's and other's.
	 * If the grids are contradictory (eg one has (3, 4, F) and the other has (3, 4, N)),
	 * the result is undefined. If the grids have overlapping grid spots, they will be collapsed into one.
	 * 
	 * [(0, 1, E); (1, 1, T)] + [(1, 0, G); (0, 0, P)] = [(0, 1, E); (1, 1, T); (1, 0, G); (0, 0, P)] 
	 * [(0, 1, E); (1, 1, T)] + [(0, 1, E); (0, 0, P)] = [(0, 1, E); (1, 1, T); (0, 0, P)] 
	 * [(0, 1, E); (1, 1, T)] + [(0, 1, Q); (0, 0, P)] = IllegalArgumentException
	 * 
	 *  This function commutes. (<code>p.gridUnion(g).equals(g.gridUnion(p))</code>).
	 *  Also, <code>p.gridUnion(p).equals(p)</code>
	 *  
	 *  Free pieces are copied over to the new Puzzle from the caller Puzzle. (Does this make sense?)
	 * 
	 * @param other	the puzzle with which to merge
	 * @return		a new puzzle that has the GridSpots of both
	 */
	public Puzzle gridUnion(Puzzle other) throws IllegalArgumentException;
	
	public boolean equals(Object other);
	
	public int hashCode();

	/**
	 * Creates a new puzzle that is the inverse of this one.
	 * Every spot that current exists will be empty.
	 * Every empty spot within the bounds will be filled with a 
	 * GridSpot containing null.
	 * 
	 * freePieces are copied.
	 * 
	 * @return	the inverse of this puzzle
	 */
	public Puzzle inverse();
	
	/**
	 * Gets one spot from the Puzzle. Which one is not specified, nor ensured to
	 * be the same between calls. Returns null if the board is empty.
	 * @return	a grid from this puzzle
	 */
	public GridSpot<Letter> getOneSpot();
	
	public Collection<Letter> freeLetters();
	
	/**
	 * Creates a new puzzle with <code>t</code> as the new content of <code>gs</code>.
	 * Assumes that gs is in the puzzle already.
	 * If t is not a free tile, this will cause an assertion error. t will be removed from the collection of free tiles.
	 * @param gs	the GridSpot to update
	 * @param t		the new content of gs
	 * @return		a Puzzle with gs filled with t
	 */
	public Puzzle fromFreeToTiles(GridSpot<Letter> gs, Letter t);

	/**
	 * Gets the number of tiles on the board with <code>r</code> as its content.
	 * @param r	may be null
	 * @return
	 */
	public int countOnBoard(Letter r);
}
